﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using MouseExtender.Logic;
using MouseExtender.Logic.Common;
using MouseExtender.Logic.Entities;
using MouseExtender.Logic.Enums;
using MouseExtender.Logic.Events;
using MouseExtender.Logic.Helpers;
using MouseExtender.Logic.Managers;

namespace MouseExtender.Controls.UserControls
{
    public partial class LauncherItemUC : UserControl
    {
        private Ellipse _firefly = null;
        private EventHandler _runOnceOnMoveAnimationComplete = null;

        public static readonly DependencyProperty DisableExtendedBehaviorProperty = DependencyProperty.Register("DisableExtendedBehavior", typeof(bool), typeof(LauncherItemUC), new UIPropertyMetadata(false, DisableExtendedBehaviorPropertyChanged));

        #region Properties

        private bool IsDragging
        {
            get;
            set;
        }

        private Point OrigianlPosition
        {
            get;
            set;
        }

        private Storyboard MoveItemAnimation
        {
            get
            {
                return (Storyboard)Resources["sbMoveItem"];
            }
        }

        private FrameworkElement ParentElement
        {
            get
            {
                return Parent as FrameworkElement; ;
            }
        }

        public TranslateTransform MoveTransform
        {
            get
            {
                return (TranslateTransform)RenderTransform;
            }
        }

        public bool DisableExtendedBehavior
        {
            get { return (bool)GetValue(DisableExtendedBehaviorProperty); }
            set { SetValue(DisableExtendedBehaviorProperty, value); }
        }

        public LauncherItem Item
        {
            get;
            set;
        }

        public string LaunchPath
        {
            get
            {
                return Item.LaunchPath;
            }
        }

        public int ItemNumber
        {
            get
            {
                return Item.ItemNumber;
            }
        }

        public bool IsBroken
        {
            get;
            set;
        }

        public string ToolTipContent
        {
            get { return toolTip.Content.ToString(); }
            set { toolTip.Content = value; }
        }

        public Color ItemColor
        {
            get
            {
                return Item.ItemColor;
            }
            set
            {
                Color stopColor = Color.FromRgb(0x50, 0x50, 0x50);
                if (value != Colors.Black)
                {
                    stopColor = value;
                }
                firstStopAnimation.To = stopColor;
                secondStopAnimation.To = stopColor;
                toolTip.Background = new SolidColorBrush(stopColor);
                Item.ItemColor = stopColor;
            }
        }

        public int TabID
        {
            get;
            set;
        }

        public bool IsUrl
        {
            get
            {
                return Item.IsUrl;
            }
            set
            {
                Item.IsUrl = value;
            }
        }

        public BitmapSource ItemBitmapSource
        {
            get
            {
                return imgIcon.Source as BitmapSource;
            }
        }

        #endregion

        public LauncherItemUC(LauncherItem item)
        {
            Item = item;
            InitializeComponent();
            ItemColor = item.ItemColor;
            MoveItemAnimation.Begin(this, true);
            MoveItemAnimation.Stop(this);
            DisableExtendedBehavior = true;
            DisableExtendedBehavior = false;
        }

        public void SetBrokenFileStyle()
        {
            //Set broken icon
            string iconUri = SettingsManager.CurrentSettings.IconSize == IconSize.Icon16x16 ? Constants.ResourcesUri.ItemBroken16x16 : Constants.ResourcesUri.ItemBroken32x32;
            imgIcon.Source = new BitmapImage(new Uri(iconUri, UriKind.Relative));
            //Set item style
            ItemColor = Constants.Defaults.BrokenItemColor;
        }

        public void RestorePosition()
        {
            ShiftBy(new Vector());
        }

        /// <summary>
        /// Animates shifting by a vector from original point
        /// </summary>
        public bool ShiftBy(Vector shift)
        {
            double currentX = MoveTransform.X;
            double currentY = MoveTransform.Y;

            DoubleAnimationUsingKeyFrames animationX = ((DoubleAnimationUsingKeyFrames)MoveItemAnimation.Children[0]);
            DoubleAnimationUsingKeyFrames animationY = ((DoubleAnimationUsingKeyFrames)MoveItemAnimation.Children[1]);

            bool isAnimationStoped = MoveItemAnimation.GetCurrentState(this) == ClockState.Stopped;
            bool isShiftEqual = animationX.KeyFrames[0].Value == shift.X && animationY.KeyFrames[0].Value == shift.Y;
            bool isAlreadyShifted = MoveTransform.X == shift.X && MoveTransform.Y == shift.Y;

            if ((!isAnimationStoped && !isShiftEqual) || (isAnimationStoped && !isAlreadyShifted))
            {
                MoveItemAnimation.Stop(this);
                MoveTransform.BeginAnimation(TranslateTransform.XProperty, null);
                MoveTransform.BeginAnimation(TranslateTransform.YProperty, null);
                MoveTransform.X = currentX;
                MoveTransform.Y = currentY;
                animationX.KeyFrames[0].Value = shift.X;
                animationY.KeyFrames[0].Value = shift.Y;
                MoveItemAnimation.Begin(this, true);
                return true;
            }
            else
            {
                return false;
            }
        }

        public void ShiftBy(Vector shift, MouseEventHandler shiftedCallback, MouseEventArgs args)
        {
            EventHandler plainCallback = new EventHandler((o, e) => shiftedCallback(o, args));
            _runOnceOnMoveAnimationComplete += plainCallback;

            bool shifting = ShiftBy(shift);
            if (!shifting) //if object will not shifts than we need to imitate animation delay.
            {
                GCHandle handle = new GCHandle();
                TimerCallback callback = (o) => { Dispatcher.Invoke(shiftedCallback, o, args); handle.Free(); };
                Timer timer = new Timer(callback, this, MoveItemAnimation.Duration.TimeSpan, TimeSpan.FromMilliseconds(-1)); //firing timer for one time
                handle = GCHandle.Alloc(timer, GCHandleType.Normal);//prevent local variabled timer from GC;
                _runOnceOnMoveAnimationComplete -= plainCallback;
            }
        }

        #region Event handlers

        private void sbMoveItem_Completed(object sender, EventArgs e)
        {
            double x = ((DoubleAnimationUsingKeyFrames)MoveItemAnimation.Children[0]).KeyFrames[0].Value;
            double y = ((DoubleAnimationUsingKeyFrames)MoveItemAnimation.Children[1]).KeyFrames[0].Value;
            MoveItemAnimation.Stop(this);
            MoveTransform.X = x;
            MoveTransform.Y = y;
            Panel.SetZIndex(this, 0);
            if (_runOnceOnMoveAnimationComplete != null)
            {
                _runOnceOnMoveAnimationComplete(this, e);
                _runOnceOnMoveAnimationComplete = null;
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDragging)
            {
                IsDragging = false;
                return;
            }

            if (ItemSelecting != null)
            {
                LaunchItemSelectingEventArgs launchingArgs = new LaunchItemSelectingEventArgs(
                    new LauncherItem()
                    {
                        LaunchPath = LaunchPath,
                        ItemNumber = ItemNumber,
                        WorkingDirectory = System.IO.Path.GetDirectoryName(LaunchPath),
                    }
                );
                ItemSelecting(this, launchingArgs);
                if (launchingArgs.LaunchingCancelled)
                    return;
            }

            string fullPath = Item.LaunchPath;
            string fullWorkingDir = Item.WorkingDirectory;
            if (!Item.IsUrl)
            {
                fullPath = PathHelper.GetFullPath(fullPath);
                fullWorkingDir = PathHelper.GetFullPath(fullWorkingDir);
            }

            ProcessStartInfo startInfo = new ProcessStartInfo(fullPath, Item.Arguments);
            if (!String.IsNullOrEmpty(fullWorkingDir))
                startInfo.WorkingDirectory = fullWorkingDir;

            App.MainWindowInstance.HideWindow();

            WaitCallback callback = (state) =>
            {
                try
                {
                    Process.Start(startInfo);
                }
                catch
                {
                    IsBroken = true;
                    Func<object> mainWindowDelegate = delegate()
                    {
                        return App.Current.MainWindow;
                    };
                    MainWindow mainWindow = (MainWindow)Dispatcher.Invoke(mainWindowDelegate);
                    Dispatcher.Invoke(new Action(SetBrokenFileStyle));
                    Dispatcher.Invoke(new Action(mainWindow.ShowWindow));
                }
            };
            ThreadPool.QueueUserWorkItem(callback);

            if (ItemSelected != null)
            {
                LaunchItemSelectedEventArgs launchedArgs = new LaunchItemSelectedEventArgs(
                    new LauncherItem()
                    {
                        LaunchPath = LaunchPath,
                        ItemNumber = ItemNumber,
                        WorkingDirectory = System.IO.Path.GetDirectoryName(LaunchPath),
                    }
                );
                ItemSelected(this, launchedArgs);
            }
            e.Handled = true;
        }

        private void UserControl_MouseEnter(object sender, MouseEventArgs e)
        {
            _firefly = new Ellipse();
            _firefly.Height = _firefly.Width = 1;
            _firefly.Fill = new SolidColorBrush(Color.FromRgb(81, 160, 192));

            Color glowColor = Color.FromRgb(0xFA, 0xFA, 0xFA);
            if (ItemColor != Color.FromRgb(0x50, 0x50, 0x50))
            {
                HSBColor itemColor = HSBColor.FromColor(System.Drawing.Color.FromArgb(ItemColor.R, ItemColor.G, ItemColor.B));
                if (itemColor.S != 0)
                {
                    HSBColor hsbColor = new HSBColor(itemColor.H, 170, 250);
                    glowColor = Color.FromRgb(hsbColor.Color.R, hsbColor.Color.G, hsbColor.Color.B);
                }
                else
                {
                    HSBColor hsbColor = new HSBColor(itemColor.H, 0, 250);
                    glowColor = Color.FromRgb(hsbColor.Color.R, hsbColor.Color.G, hsbColor.Color.B);
                }
            }

            OuterGlowBitmapEffect firstEffect = new OuterGlowBitmapEffect();
            firstEffect.GlowColor = glowColor;
            firstEffect.GlowSize = 25;
            OuterGlowBitmapEffect secondEffect = new OuterGlowBitmapEffect();
            secondEffect.GlowColor = glowColor;
            secondEffect.GlowSize = 15;
            OuterGlowBitmapEffect thirdEffect = new OuterGlowBitmapEffect();
            thirdEffect.GlowColor = glowColor;
            thirdEffect.GlowSize = 7;
            BitmapEffectGroup effectGroup = new BitmapEffectGroup();
            effectGroup.Children.Add(firstEffect);
            effectGroup.Children.Add(secondEffect);
            effectGroup.Children.Add(thirdEffect);

            _firefly.BitmapEffect = (BitmapEffect)effectGroup;

#warning //TODO: in order to avoid high cpu usage replace sofware BitmapEffect with hardware Effect. Sample below. Need to group and assign.
            //DropShadowEffect glow = new DropShadowEffect()
            //{
            //    ShadowDepth = 0,
            //    BlurRadius = 50,
            //    Color = glowColor
            //};
            //_firefly.Effect = glow;

            Point cursorPosition = e.GetPosition(this);
            _firefly.SetValue(Canvas.TopProperty, cursorPosition.Y);
            _firefly.SetValue(Canvas.LeftProperty, cursorPosition.X);
            iconPlaceholder.Children.Add(_firefly);
        }

        private void UserControl_MouseLeave(object sender, MouseEventArgs e)
        {
            iconPlaceholder.Children.Remove(_firefly);
            _firefly = null;
        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            Point cursorPosition = e.GetPosition(this);
            _firefly.SetValue(Canvas.TopProperty, cursorPosition.Y);
            _firefly.SetValue(Canvas.LeftProperty, cursorPosition.X);
        }

        private void UserControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            OrigianlPosition = e.GetPosition(this);
        }

        private void UserControl_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && !DisableExtendedBehavior)
            {
                Point currentPosition = e.GetPosition(this);
                double deltaX = currentPosition.X - OrigianlPosition.X;
                double deltaY = currentPosition.Y - OrigianlPosition.Y;

                //snapping to 5 pixels
                if (!IsDragging)
                {
                    deltaX = Math.Round(deltaX / 5) * 5;
                    deltaY = Math.Round(deltaY / 5) * 5;
                }
                if (deltaX != 0 || deltaY != 0)
                    IsDragging = true;

                //handling overrun
                Point parentPosition = e.GetPosition(ParentElement);
                Point itemPosition = new Point(parentPosition.X - currentPosition.X, parentPosition.Y - currentPosition.Y);

                //expected position after move;
                itemPosition.X += deltaX;
                itemPosition.Y += deltaY;
                //check that item inside placeholder
                double xOverlap = itemPosition.X < 0 ? itemPosition.X : (itemPosition.X + ActualWidth > ParentElement.ActualWidth ? itemPosition.X + ActualWidth - ParentElement.ActualWidth : 0);
                double yOverlap = itemPosition.Y < 0 ? itemPosition.Y : (itemPosition.Y + ActualHeight > ParentElement.ActualHeight ? itemPosition.Y + ActualHeight - ParentElement.ActualHeight : 0);
                //if item expected to be outside the placeholder then correct position
                if (xOverlap != 0)
                    deltaX -= xOverlap;
                if (yOverlap != 0)
                    deltaY -= yOverlap;

                Panel.SetZIndex(this, 1);
                ((TranslateTransform)RenderTransform).X += deltaX;
                ((TranslateTransform)RenderTransform).Y += deltaY;

                if (ItemDragging != null)
                {
                    ItemDragging(this, e);
                }
            }
        }

        private void UserControl_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (IsDragging)
            {
                Point p = e.GetPosition(itemButton);
                if (p.X < 0 || p.X > itemButton.ActualWidth || p.Y < 0 || p.Y > itemButton.ActualHeight)
                    IsDragging = false;

                if (ItemDropped != null)
                    ItemDropped(this, e);
            }
        }

        private static void DisableExtendedBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            LauncherItemUC item = (LauncherItemUC)d;
            bool newValue = (bool)e.NewValue;

            item.contextMenu.IsEnabled = !newValue;
            item.contextMenu.Visibility = newValue ? Visibility.Collapsed : Visibility.Visible;
            item.ChangeExtendedEventsSubscription(newValue);
        }

        private void ChangeExtendedEventsSubscription(bool disableExtendedBehavior)
        {
            if (disableExtendedBehavior)
            {
                PreviewMouseMove -= UserControl_PreviewMouseMove;
            }
            else
            {
                PreviewMouseMove += UserControl_PreviewMouseMove;
            }
        }

        #endregion

        public event LaunchItemSelected ItemSelected;
        public event LaunchItemSelecting ItemSelecting;
        public event MouseEventHandler ItemDragging;
        public event MouseEventHandler ItemDropped;
    }
}
